0. PACKAGE LOAD AND MARKDOWN CONFIGURATION

library(tidyverse)
library(caret)
library(future)
library(doParallel)
library(heatmaply)
library(factoextra)
library(FactoMineR)
library(nnet)
library(future)
library(doParallel)
library(e1071)

1. DATA DOWNLOAD

The data will be downloaded using the link from Coursera page.

fileLinkTraining <- "https://d396qusza40orc.cloudfront.net/predmachlearn/pml-training.csv" 

fileLinkTest <- "https://d396qusza40orc.cloudfront.net/predmachlearn/pml-testing.csv"

harTraining <- read.csv(fileLinkTraining)

harTest <- read.csv(fileLinkTest)

2. DATA WRANGLING

First columns from both dataset (user name, time stamps) will be remove since the model shall be independent from the person who uses it and it is not a time series. Data on test set has several empty columns and “pure NA´s” columns. This ones will be removed from both training set. Columns with no variation at test set will also be removed.

not_all_na <- function(x) any(!is.na(x)) # function to determine if the column has all values like NA´s

harTstClean <-
        harTest %>%
        select(-c("X", 
                  "user_name", 
                  "raw_timestamp_part_1", 
                  "raw_timestamp_part_2", 
                  "cvtd_timestamp",
                  "problem_id",
                  "num_window")) %>%
        select_if(not_all_na) %>%
        select_if(~n_distinct(.) > 1)

harTrnClean <- harTraining[ ,c(names(harTstClean), "classe")]

The training dataset will be split to allow model test and validation at a proportion to 60/20/20 %.

inVal = createDataPartition(harTrnClean$classe, p = 0.2, list = F)

val <- harTrnClean[inVal, ]

model <- harTrnClean[-inVal, ]

inTrain <- createDataPartition(model$classe, p = (0.6/0.8), list = F)

train <- model[inTrain, ]

test <- model[-inTrain, ]

3. EDA

3.1 Null model performance

Pre-model tasks are related evaluate the Null Model predictions. This will be accomplished considering the most frequent class in all “predictions”, generating a lower limit for any model that will be created, as suggest by Zumel and Mount (2014).

nullPred <- test %>% select("classe")

nullPred$pred.class <- names(sort(table(nullPred$classe), decreasing = TRUE)[1])

print(confusionMatrix(as.factor(nullPred$pred.class), reference = as.factor(nullPred$classe)))
Confusion Matrix and Statistics

          Reference
Prediction    A    B    C    D    E
         A 1116  759  684  643  721
         B    0    0    0    0    0
         C    0    0    0    0    0
         D    0    0    0    0    0
         E    0    0    0    0    0

Overall Statistics
                                          
               Accuracy : 0.2845          
                 95% CI : (0.2704, 0.2989)
    No Information Rate : 0.2845          
    P-Value [Acc > NIR] : 0.506           
                                          
                  Kappa : 0               
                                          
 Mcnemar's Test P-Value : NA              

Statistics by Class:

                     Class: A Class: B Class: C Class: D Class: E
Sensitivity            1.0000   0.0000   0.0000   0.0000   0.0000
Specificity            0.0000   1.0000   1.0000   1.0000   1.0000
Pos Pred Value         0.2845      NaN      NaN      NaN      NaN
Neg Pred Value            NaN   0.8065   0.8256   0.8361   0.8162
Prevalence             0.2845   0.1935   0.1744   0.1639   0.1838
Detection Rate         0.2845   0.0000   0.0000   0.0000   0.0000
Detection Prevalence   1.0000   0.0000   0.0000   0.0000   0.0000
Balanced Accuracy      0.5000   0.5000   0.5000   0.5000   0.5000

3.2 Covariates correlation

The multicolinearity (covariates correlation) will be investigated, since it can be harmful for some kind of models, like logistic regression. To investigate, a the heatmaply_cor (from package heatmaply) will be user so related covariates will also be grouped together using a hierarchical cluster technique.

corrMat <- harTrnClean %>% select(-classe) %>% mutate_if(is.integer, as.numeric) %>% cor()

heatmaply(corrMat, symm = TRUE, cexRow = .0001, cexCol = .0001, branches_lwd = .1)

For the plot, it is possible to see that very few covariates presents correlation.

pcaCov <- harTrnClean %>% select(-classe) %>% PCA(scale.unit = TRUE, graph = FALSE)

get_eigenvalue(pcaCov)
        eigenvalue variance.percent cumulative.variance.percent
Dim.1  8.356480510      16.07015483                    16.07015
Dim.2  8.103311777      15.58329188                    31.65345
Dim.3  4.676019495       8.99234518                    40.64579
Dim.4  4.129637592       7.94161075                    48.58740
Dim.5  3.651958340       7.02299681                    55.61040
Dim.6  3.003559604       5.77607616                    61.38648
Dim.7  2.239960734       4.30761680                    65.69409
Dim.8  2.072819572       3.98619149                    69.68028
Dim.9  1.717230735       3.30236680                    72.98265
Dim.10 1.508821495       2.90157980                    75.88423
Dim.11 1.385497930       2.66441910                    78.54865
Dim.12 1.129241536       2.17161834                    80.72027
Dim.13 0.986674562       1.89745108                    82.61772
Dim.14 0.890702735       1.71288987                    84.33061
Dim.15 0.836058641       1.60780508                    85.93841
Dim.16 0.789251336       1.51779103                    87.45620
Dim.17 0.677935082       1.30372131                    88.75993
Dim.18 0.609720195       1.17253884                    89.93247
Dim.19 0.532431274       1.02390630                    90.95637
Dim.20 0.484840952       0.93238645                    91.88876
Dim.21 0.425640834       0.81854007                    92.70730
Dim.22 0.398595212       0.76652925                    93.47383
Dim.23 0.382694691       0.73595133                    94.20978
Dim.24 0.339300889       0.65250171                    94.86228
Dim.25 0.307706521       0.59174331                    95.45402
Dim.26 0.292964236       0.56339276                    96.01742
Dim.27 0.255991614       0.49229157                    96.50971
Dim.28 0.236252841       0.45433239                    96.96404
Dim.29 0.203445985       0.39124228                    97.35528
Dim.30 0.179889105       0.34594059                    97.70122
Dim.31 0.170113804       0.32714193                    98.02837
Dim.32 0.131742348       0.25335067                    98.28172
Dim.33 0.121832106       0.23429251                    98.51601
Dim.34 0.112447021       0.21624427                    98.73225
Dim.35 0.091981456       0.17688742                    98.90914
Dim.36 0.079718822       0.15330543                    99.06245
Dim.37 0.064211387       0.12348344                    99.18593
Dim.38 0.056537299       0.10872558                    99.29465
Dim.39 0.055188020       0.10613081                    99.40079
Dim.40 0.040801837       0.07846507                    99.47925
Dim.41 0.038103474       0.07327591                    99.55253
Dim.42 0.035457709       0.06818790                    99.62071
Dim.43 0.033727650       0.06486087                    99.68557
Dim.44 0.032215407       0.06195270                    99.74753
Dim.45 0.028716975       0.05522495                    99.80275
Dim.46 0.026853460       0.05164127                    99.85439
Dim.47 0.021661899       0.04165750                    99.89605
Dim.48 0.020595887       0.03960747                    99.93566
Dim.49 0.013471638       0.02590700                    99.96157
Dim.50 0.011875304       0.02283712                    99.98440
Dim.51 0.005961537       0.01146449                    99.99587
Dim.52 0.002148931       0.00413256                   100.00000
fviz_eig(pcaCov)

Although the correlation between covariates is not big, a principal components analysis show that with only 10 components (from 52) it is possible to explain 90% of the total variation.

4. MODELING

All modeling will be done considering a parallel computation using doParallel package.

workers <- availableCores() - 1

4.1 Multinomial Regression

The first model that will be tested it is multinomial regression. Two models will be done: (1) all with centar an scale and with and without principal component as a pre-processing.

Only Center and Scale

cl <- makeClusterPSOCK(workers)
registerDoParallel(cl)

mdlLrSc <- train(classe ~., data = train, method = 'multinom', preProcess = c("center","scale"))
stopCluster(cl)

registerDoSEQ()
lrScPred <- predict.train(mdlLrSc, newdata = test)

print(confusionMatrix(lrScPred, reference = as.factor(test$classe)))
Confusion Matrix and Statistics

          Reference
Prediction   A   B   C   D   E
         A 932  89  54  40  32
         B  51 526  71  28 106
         C  62  70 479  86  48
         D  57  15  52 463  70
         E  14  59  28  26 465

Overall Statistics
                                          
               Accuracy : 0.7303          
                 95% CI : (0.7161, 0.7441)
    No Information Rate : 0.2845          
    P-Value [Acc > NIR] : < 2.2e-16       
                                          
                  Kappa : 0.6586          
                                          
 Mcnemar's Test P-Value : 1.803e-11       

Statistics by Class:

                     Class: A Class: B Class: C Class: D Class: E
Sensitivity            0.8351   0.6930   0.7003   0.7201   0.6449
Specificity            0.9234   0.9191   0.9179   0.9409   0.9603
Pos Pred Value         0.8126   0.6726   0.6430   0.7047   0.7855
Neg Pred Value         0.9337   0.9258   0.9355   0.9449   0.9231
Prevalence             0.2845   0.1935   0.1744   0.1639   0.1838
Detection Rate         0.2376   0.1341   0.1221   0.1180   0.1185
Detection Prevalence   0.2924   0.1993   0.1899   0.1675   0.1509
Balanced Accuracy      0.8793   0.8061   0.8091   0.8305   0.8026

The multinomial regression, centering and scalling the variables, was able to achieve a accuracy of 0,73 on test dataset.

Center, Scale and PCA

cl <- makeClusterPSOCK(workers)
registerDoParallel(cl)

mdlLrPCA <- train(classe ~., data = train, method = 'multinom', preProcess = c("center", "scale", "pca"))
stopCluster(cl)

registerDoSEQ()
lrPCAPred <- predict.train(mdlLrPCA, newdata = test)

print(confusionMatrix(lrPCAPred, reference = as.factor(test$classe)))
Confusion Matrix and Statistics

          Reference
Prediction   A   B   C   D   E
         A 729 158 166  65  64
         B  97 332  84  51 143
         C 110 119 332  86  83
         D 147  63  76 372  92
         E  33  87  26  69 339

Overall Statistics
                                         
               Accuracy : 0.5363         
                 95% CI : (0.5206, 0.552)
    No Information Rate : 0.2845         
    P-Value [Acc > NIR] : < 2.2e-16      
                                         
                  Kappa : 0.4128         
                                         
 Mcnemar's Test P-Value : < 2.2e-16      

Statistics by Class:

                     Class: A Class: B Class: C Class: D Class: E
Sensitivity            0.6532  0.43742  0.48538  0.57854  0.47018
Specificity            0.8386  0.88148  0.87712  0.88476  0.93285
Pos Pred Value         0.6168  0.46959  0.45479  0.49600  0.61191
Neg Pred Value         0.8588  0.86723  0.88976  0.91459  0.88661
Prevalence             0.2845  0.19347  0.17436  0.16391  0.18379
Detection Rate         0.1858  0.08463  0.08463  0.09483  0.08641
Detection Prevalence   0.3013  0.18022  0.18608  0.19118  0.14122
Balanced Accuracy      0.7459  0.65945  0.68125  0.73165  0.70152

Using PCA as a pre-processing have decreased the accuracy on test set.

4.2 Random Forest

To improve the prediction capabilities, a random forest will be used, considering all hyperparameters as defautl values.

cl <- makeClusterPSOCK(workers)

registerDoParallel(cl)

mdlRf <- train(classe ~., data = train, method = 'ranger')
rfPred <- predict.train(mdlRf, newdata = test)

print(confusionMatrix(mdlRf, reference = as.factor(test$classe)))
Bootstrapped (25 reps) Confusion Matrix 

(entries are percentual average cell counts across resamples)
 
          Reference
Prediction    A    B    C    D    E
         A 28.4  0.1  0.0  0.0  0.0
         B  0.0 19.2  0.2  0.0  0.0
         C  0.0  0.1 17.1  0.2  0.0
         D  0.0  0.0  0.1 16.0  0.1
         E  0.0  0.0  0.0  0.0 18.3
                            
 Accuracy (average) : 0.9901

USing only the default values of hyperparameters, it was possible to increase the accuracy to 0.99. To have a second view of the performance, the model will be tested on validation data set.

rfval <- predict.train(mdlRf, newdata = val)

print(confusionMatrix(mdlRf, reference = as.factor(val$classe)))
Bootstrapped (25 reps) Confusion Matrix 

(entries are percentual average cell counts across resamples)
 
          Reference
Prediction    A    B    C    D    E
         A 28.4  0.1  0.0  0.0  0.0
         B  0.0 19.2  0.2  0.0  0.0
         C  0.0  0.1 17.1  0.2  0.0
         D  0.0  0.0  0.1 16.0  0.1
         E  0.0  0.0  0.0  0.0 18.3
                            
 Accuracy (average) : 0.9901

5. FINAL MODEL EVALUATION

Final prediction for 20 selected cases.

finalPred <- predict.train(mdlRf, newdata = harTstClean)

finalPred
 [1] B A B A A E D B A A B C B A E E A B B B
Levels: A B C D E
LS0tDQp0aXRsZTogIkhBUiBDb3Vyc2VyYSBKb2huwrRzIEhvcGtpbnMgUHJhdGljYWwgTUwiDQphdXRob3I6ICJTYW11ZWwgQm96emkgQmFjbyINCm91dHB1dDoNCiAgcGRmX2RvY3VtZW50OiBkZWZhdWx0DQogIGh0bWxfbm90ZWJvb2s6IGRlZmF1bHQNCmFsd2F5c19hbGxvd19odG1sOiB0cnVlDQotLS0NCiMjIDAuIFBBQ0tBR0UgTE9BRCBBTkQgTUFSS0RPV04gQ09ORklHVVJBVElPTg0KDQpgYGB7ciBQQUNLQUdFIExPQUQsIGVjaG89VCwgbWVzc2FnZT1GLCB3YXJuaW5nPUZ9DQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkoY2FyZXQpDQpsaWJyYXJ5KGZ1dHVyZSkNCmxpYnJhcnkoZG9QYXJhbGxlbCkNCmxpYnJhcnkoaGVhdG1hcGx5KQ0KbGlicmFyeShmYWN0b2V4dHJhKQ0KbGlicmFyeShGYWN0b01pbmVSKQ0KbGlicmFyeShubmV0KQ0KbGlicmFyeShmdXR1cmUpDQpsaWJyYXJ5KGRvUGFyYWxsZWwpDQpsaWJyYXJ5KGUxMDcxKQ0KYGBgDQojIyAxLiBEQVRBIERPV05MT0FEDQoNClRoZSBkYXRhIHdpbGwgYmUgZG93bmxvYWRlZCB1c2luZyB0aGUgbGluayBmcm9tIENvdXJzZXJhIHBhZ2UuDQpgYGB7ciBEQVRBIERPV05MT0FELCBjYWNoZSA9IFRSVUV9DQpmaWxlTGlua1RyYWluaW5nIDwtICJodHRwczovL2QzOTZxdXN6YTQwb3JjLmNsb3VkZnJvbnQubmV0L3ByZWRtYWNobGVhcm4vcG1sLXRyYWluaW5nLmNzdiIgDQoNCmZpbGVMaW5rVGVzdCA8LSAiaHR0cHM6Ly9kMzk2cXVzemE0MG9yYy5jbG91ZGZyb250Lm5ldC9wcmVkbWFjaGxlYXJuL3BtbC10ZXN0aW5nLmNzdiINCg0KaGFyVHJhaW5pbmcgPC0gcmVhZC5jc3YoZmlsZUxpbmtUcmFpbmluZykNCg0KaGFyVGVzdCA8LSByZWFkLmNzdihmaWxlTGlua1Rlc3QpDQpgYGANCiMjIDIuIERBVEEgV1JBTkdMSU5HDQoNCkZpcnN0IGNvbHVtbnMgZnJvbSBib3RoIGRhdGFzZXQgKHVzZXIgbmFtZSwgdGltZSBzdGFtcHMpIHdpbGwgYmUgcmVtb3ZlIHNpbmNlIHRoZSBtb2RlbCBzaGFsbCBiZSBpbmRlcGVuZGVudCBmcm9tIHRoZSBwZXJzb24gd2hvIHVzZXMgaXQgYW5kIGl0IGlzIG5vdCBhIHRpbWUgc2VyaWVzLiBEYXRhIG9uIHRlc3Qgc2V0IGhhcyBzZXZlcmFsIGVtcHR5IGNvbHVtbnMgYW5kIOKAnHB1cmUgTkHCtHPigJ0gY29sdW1ucy4gVGhpcyBvbmVzIHdpbGwgYmUgcmVtb3ZlZCBmcm9tIGJvdGggdHJhaW5pbmcgc2V0LiBDb2x1bW5zIHdpdGggbm8gdmFyaWF0aW9uIGF0IHRlc3Qgc2V0IHdpbGwgYWxzbyBiZSByZW1vdmVkLg0KDQpgYGB7ciBDT0xVTU4gU0VMRUNUSU9OLCBjYWNoZSA9IFRSVUV9DQpub3RfYWxsX25hIDwtIGZ1bmN0aW9uKHgpIGFueSghaXMubmEoeCkpICMgZnVuY3Rpb24gdG8gZGV0ZXJtaW5lIGlmIHRoZSBjb2x1bW4gaGFzIGFsbCB2YWx1ZXMgbGlrZSBOQcK0cw0KDQpoYXJUc3RDbGVhbiA8LQ0KICAgICAgICBoYXJUZXN0ICU+JQ0KICAgICAgICBzZWxlY3QoLWMoIlgiLCANCiAgICAgICAgICAgICAgICAgICJ1c2VyX25hbWUiLCANCiAgICAgICAgICAgICAgICAgICJyYXdfdGltZXN0YW1wX3BhcnRfMSIsIA0KICAgICAgICAgICAgICAgICAgInJhd190aW1lc3RhbXBfcGFydF8yIiwgDQogICAgICAgICAgICAgICAgICAiY3Z0ZF90aW1lc3RhbXAiLA0KICAgICAgICAgICAgICAgICAgInByb2JsZW1faWQiLA0KICAgICAgICAgICAgICAgICAgIm51bV93aW5kb3ciKSkgJT4lDQogICAgICAgIHNlbGVjdF9pZihub3RfYWxsX25hKSAlPiUNCiAgICAgICAgc2VsZWN0X2lmKH5uX2Rpc3RpbmN0KC4pID4gMSkNCg0KaGFyVHJuQ2xlYW4gPC0gaGFyVHJhaW5pbmdbICxjKG5hbWVzKGhhclRzdENsZWFuKSwgImNsYXNzZSIpXQ0KYGBgDQpUaGUgdHJhaW5pbmcgZGF0YXNldCB3aWxsIGJlIHNwbGl0IHRvIGFsbG93IG1vZGVsIHRlc3QgYW5kIHZhbGlkYXRpb24gYXQgYSBwcm9wb3J0aW9uIHRvIDYwLzIwLzIwICUuIA0KYGBge3IgREFUQVNFVCBTUExJVCwgY2FjaGUgPSBUUlVFfQ0KaW5WYWwgPSBjcmVhdGVEYXRhUGFydGl0aW9uKGhhclRybkNsZWFuJGNsYXNzZSwgcCA9IDAuMiwgbGlzdCA9IEYpDQoNCnZhbCA8LSBoYXJUcm5DbGVhbltpblZhbCwgXQ0KDQptb2RlbCA8LSBoYXJUcm5DbGVhblstaW5WYWwsIF0NCg0KaW5UcmFpbiA8LSBjcmVhdGVEYXRhUGFydGl0aW9uKG1vZGVsJGNsYXNzZSwgcCA9ICgwLjYvMC44KSwgbGlzdCA9IEYpDQoNCnRyYWluIDwtIG1vZGVsW2luVHJhaW4sIF0NCg0KdGVzdCA8LSBtb2RlbFstaW5UcmFpbiwgXQ0KYGBgDQojIyAzLiBFREENCg0KIyMjIDMuMSBOdWxsIG1vZGVsIHBlcmZvcm1hbmNlDQoNClByZS1tb2RlbCB0YXNrcyBhcmUgcmVsYXRlZCBldmFsdWF0ZSB0aGUgTnVsbCBNb2RlbCBwcmVkaWN0aW9ucy4gVGhpcyB3aWxsIGJlIGFjY29tcGxpc2hlZCBjb25zaWRlcmluZyB0aGUgbW9zdCBmcmVxdWVudCBjbGFzcyBpbiBhbGwg4oCccHJlZGljdGlvbnPigJ0sIGdlbmVyYXRpbmcgYSBsb3dlciBsaW1pdCBmb3IgYW55IG1vZGVsIHRoYXQgd2lsbCBiZSBjcmVhdGVkLCBhcyBzdWdnZXN0IGJ5IFp1bWVsIGFuZCBNb3VudCAoMjAxNCkuIA0KYGBge3IgTlVMTCBNT0RFTCBQRVJGT1JNQU5DRSwgbWVzc2FnZT1GLCB3YXJuaW5nPUYsY2FjaGU9VH0NCm51bGxQcmVkIDwtIHRlc3QgJT4lIHNlbGVjdCgiY2xhc3NlIikNCg0KbnVsbFByZWQkcHJlZC5jbGFzcyA8LSBuYW1lcyhzb3J0KHRhYmxlKG51bGxQcmVkJGNsYXNzZSksIGRlY3JlYXNpbmcgPSBUUlVFKVsxXSkNCg0KcHJpbnQoY29uZnVzaW9uTWF0cml4KGFzLmZhY3RvcihudWxsUHJlZCRwcmVkLmNsYXNzKSwgcmVmZXJlbmNlID0gYXMuZmFjdG9yKG51bGxQcmVkJGNsYXNzZSkpKQ0KYGBgDQoNCiMgMy4yIENvdmFyaWF0ZXMgY29ycmVsYXRpb24NCg0KVGhlIG11bHRpY29saW5lYXJpdHkgKGNvdmFyaWF0ZXMgY29ycmVsYXRpb24pIHdpbGwgYmUgaW52ZXN0aWdhdGVkLCBzaW5jZSBpdCBjYW4gYmUgaGFybWZ1bCBmb3Igc29tZSBraW5kIG9mIG1vZGVscywgbGlrZSBsb2dpc3RpYyByZWdyZXNzaW9uLiBUbyBpbnZlc3RpZ2F0ZSwgYSB0aGUgaGVhdG1hcGx5X2NvciAoZnJvbSBwYWNrYWdlIGhlYXRtYXBseSkgd2lsbCBiZSB1c2VyIHNvIHJlbGF0ZWQgY292YXJpYXRlcyB3aWxsIGFsc28gYmUgZ3JvdXBlZCB0b2dldGhlciB1c2luZyBhIGhpZXJhcmNoaWNhbCBjbHVzdGVyIHRlY2huaXF1ZS4gDQoNCmBgYHtyIE1VTFRJQ09MSU5FQVJJVFksIGNhY2hlPVR9DQpjb3JyTWF0IDwtIGhhclRybkNsZWFuICU+JSBzZWxlY3QoLWNsYXNzZSkgJT4lIG11dGF0ZV9pZihpcy5pbnRlZ2VyLCBhcy5udW1lcmljKSAlPiUgY29yKCkNCg0KaGVhdG1hcGx5KGNvcnJNYXQsIHN5bW0gPSBUUlVFLCBjZXhSb3cgPSAuMDAwMSwgY2V4Q29sID0gLjAwMDEsIGJyYW5jaGVzX2x3ZCA9IC4xKQ0KYGBgDQpGb3IgdGhlIHBsb3QsIGl0IGlzIHBvc3NpYmxlIHRvIHNlZSB0aGF0IHZlcnkgZmV3IGNvdmFyaWF0ZXMgcHJlc2VudHMgY29ycmVsYXRpb24uIA0KDQpgYGB7ciBQQ0EgQ09WQVJJQVRFUywgY2FjaGU9VH0NCnBjYUNvdiA8LSBoYXJUcm5DbGVhbiAlPiUgc2VsZWN0KC1jbGFzc2UpICU+JSBQQ0Eoc2NhbGUudW5pdCA9IFRSVUUsIGdyYXBoID0gRkFMU0UpDQoNCmdldF9laWdlbnZhbHVlKHBjYUNvdikNCg0KZnZpel9laWcocGNhQ292KQ0KYGBgDQpBbHRob3VnaCB0aGUgY29ycmVsYXRpb24gYmV0d2VlbiBjb3ZhcmlhdGVzIGlzIG5vdCBiaWcsIGEgcHJpbmNpcGFsIGNvbXBvbmVudHMgYW5hbHlzaXMgc2hvdyB0aGF0IHdpdGggb25seSAxMCBjb21wb25lbnRzIChmcm9tIDUyKSBpdCBpcyBwb3NzaWJsZSB0byBleHBsYWluIDkwJSBvZiB0aGUgdG90YWwgdmFyaWF0aW9uLg0KDQojIyA0LiBNT0RFTElORw0KDQpBbGwgbW9kZWxpbmcgd2lsbCBiZSBkb25lIGNvbnNpZGVyaW5nIGEgcGFyYWxsZWwgY29tcHV0YXRpb24gdXNpbmcgKipkb1BhcmFsbGVsKiogcGFja2FnZS4NCmBgYHtyIFBBUkFMRUxMSVpBVElPTn0NCndvcmtlcnMgPC0gYXZhaWxhYmxlQ29yZXMoKSAtIDENCmBgYA0KIyMjIDQuMSBNdWx0aW5vbWlhbCBSZWdyZXNzaW9uDQoNClRoZSBmaXJzdCBtb2RlbCB0aGF0IHdpbGwgYmUgdGVzdGVkIGl0IGlzIG11bHRpbm9taWFsIHJlZ3Jlc3Npb24uIFR3byBtb2RlbHMgd2lsbCBiZSBkb25lOiAoMSkgYWxsIHdpdGggY2VudGFyIGFuIHNjYWxlIGFuZCB3aXRoIGFuZCB3aXRob3V0IHByaW5jaXBhbCBjb21wb25lbnQgYXMgYSBwcmUtcHJvY2Vzc2luZy4NCg0KIyMjIyBPbmx5IENlbnRlciBhbmQgU2NhbGUNCg0KYGBge3IgTVIgQ0VOVEVSIFNDQUxFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUYsIGVycm9yPUZBTFNFLCBjYWNoZT1ULCByZXN1bHRzPSdoaWRlJ30NCmNsIDwtIG1ha2VDbHVzdGVyUFNPQ0sod29ya2VycykNCg0KcmVnaXN0ZXJEb1BhcmFsbGVsKGNsKQ0KDQptZGxMclNjIDwtIHRyYWluKGNsYXNzZSB+LiwgZGF0YSA9IHRyYWluLCBtZXRob2QgPSAnbXVsdGlub20nLCBwcmVQcm9jZXNzID0gYygiY2VudGVyIiwic2NhbGUiKSkNCg0Kc3RvcENsdXN0ZXIoY2wpDQoNCnJlZ2lzdGVyRG9TRVEoKQ0KYGBgDQpgYGB7ciBNUiBQUkVESUNUSU9OUywgY2FjaGU9VH0NCmxyU2NQcmVkIDwtIHByZWRpY3QudHJhaW4obWRsTHJTYywgbmV3ZGF0YSA9IHRlc3QpDQoNCnByaW50KGNvbmZ1c2lvbk1hdHJpeChsclNjUHJlZCwgcmVmZXJlbmNlID0gYXMuZmFjdG9yKHRlc3QkY2xhc3NlKSkpDQpgYGANClRoZSBtdWx0aW5vbWlhbCByZWdyZXNzaW9uLCBjZW50ZXJpbmcgYW5kIHNjYWxsaW5nIHRoZSB2YXJpYWJsZXMsIHdhcyBhYmxlIHRvIGFjaGlldmUgYSBhY2N1cmFjeSBvZiAwLDczIG9uIHRlc3QgZGF0YXNldC4NCg0KIyMjIyBDZW50ZXIsIFNjYWxlIGFuZCBQQ0ENCmBgYHtyIE1SIFBDQSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GLCBlcnJvcj1GQUxTRSwgY2FjaGU9VCwgcmVzdWx0cz0naGlkZSd9DQpjbCA8LSBtYWtlQ2x1c3RlclBTT0NLKHdvcmtlcnMpDQoNCnJlZ2lzdGVyRG9QYXJhbGxlbChjbCkNCg0KbWRsTHJQQ0EgPC0gdHJhaW4oY2xhc3NlIH4uLCBkYXRhID0gdHJhaW4sIG1ldGhvZCA9ICdtdWx0aW5vbScsIHByZVByb2Nlc3MgPSBjKCJjZW50ZXIiLCAic2NhbGUiLCAicGNhIikpDQoNCnN0b3BDbHVzdGVyKGNsKQ0KDQpyZWdpc3RlckRvU0VRKCkNCmBgYA0KYGBge3IgTVIgUENBIFBSRURJQ1RJT05TLCBjYWNoZT1UfQ0KbHJQQ0FQcmVkIDwtIHByZWRpY3QudHJhaW4obWRsTHJQQ0EsIG5ld2RhdGEgPSB0ZXN0KQ0KDQpwcmludChjb25mdXNpb25NYXRyaXgobHJQQ0FQcmVkLCByZWZlcmVuY2UgPSBhcy5mYWN0b3IodGVzdCRjbGFzc2UpKSkNCmBgYA0KVXNpbmcgUENBIGFzIGEgcHJlLXByb2Nlc3NpbmcgaGF2ZSBkZWNyZWFzZWQgdGhlIGFjY3VyYWN5IG9uIHRlc3Qgc2V0LiANCg0KIyMjIDQuMiBSYW5kb20gRm9yZXN0DQoNClRvIGltcHJvdmUgdGhlIHByZWRpY3Rpb24gY2FwYWJpbGl0aWVzLCBhIHJhbmRvbSBmb3Jlc3Qgd2lsbCBiZSB1c2VkLCBjb25zaWRlcmluZyBhbGwgaHlwZXJwYXJhbWV0ZXJzIGFzIGRlZmF1dGwgdmFsdWVzLg0KDQpgYGB7ciBSQU5ET00gRk9SRVNULCBjYWNoZT1ULCBtZXNzYWdlPUYsIGVycm9yPUYsIHdhcm5pbmc9RiwgcmVzdWx0cz0naGlkZSd9DQpjbCA8LSBtYWtlQ2x1c3RlclBTT0NLKHdvcmtlcnMpDQoNCnJlZ2lzdGVyRG9QYXJhbGxlbChjbCkNCg0KbWRsUmYgPC0gdHJhaW4oY2xhc3NlIH4uLCBkYXRhID0gdHJhaW4sIG1ldGhvZCA9ICdyYW5nZXInKQ0KDQpzdG9wQ2x1c3RlcihjbCkNCg0KcmVnaXN0ZXJEb1NFUSgpDQpgYGANCmBgYHtyIFJGIFBSRURJQ1RJT059DQpyZlByZWQgPC0gcHJlZGljdC50cmFpbihtZGxSZiwgbmV3ZGF0YSA9IHRlc3QpDQoNCnByaW50KGNvbmZ1c2lvbk1hdHJpeChtZGxSZiwgcmVmZXJlbmNlID0gYXMuZmFjdG9yKHRlc3QkY2xhc3NlKSkpDQpgYGANClVTaW5nIG9ubHkgdGhlIGRlZmF1bHQgdmFsdWVzIG9mIGh5cGVycGFyYW1ldGVycywgaXQgd2FzIHBvc3NpYmxlIHRvIGluY3JlYXNlIHRoZSBhY2N1cmFjeSB0byAwLjk5LiBUbyBoYXZlIGEgc2Vjb25kIHZpZXcgb2YgdGhlIHBlcmZvcm1hbmNlLCB0aGUgbW9kZWwgd2lsbCBiZSB0ZXN0ZWQgb24gdmFsaWRhdGlvbiBkYXRhIHNldC4NCmBgYHtyIFJGIFZBTElEQVRJT059DQpyZnZhbCA8LSBwcmVkaWN0LnRyYWluKG1kbFJmLCBuZXdkYXRhID0gdmFsKQ0KDQpwcmludChjb25mdXNpb25NYXRyaXgobWRsUmYsIHJlZmVyZW5jZSA9IGFzLmZhY3Rvcih2YWwkY2xhc3NlKSkpDQpgYGANCiMjIDUuIEZJTkFMIE1PREVMIEVWQUxVQVRJT04NCg0KRmluYWwgcHJlZGljdGlvbiBmb3IgMjAgc2VsZWN0ZWQgY2FzZXMuDQoNCmBgYHtyIEZJTkFMIFBSRURJQ1RJT059DQpmaW5hbFByZWQgPC0gcHJlZGljdC50cmFpbihtZGxSZiwgbmV3ZGF0YSA9IGhhclRzdENsZWFuKQ0KDQpmaW5hbFByZWQNCmBgYA0KDQo=